package com.ruoyi.system.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.config.MinIoProperties;
import com.ruoyi.common.minio.MinIoUtil;
import com.ruoyi.common.minio.MyMinioClient;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.File;
import com.ruoyi.system.domain.dto.FileUploadDTO;
import com.ruoyi.system.domain.enums.IsFinish;
import com.ruoyi.system.domain.vo.FileUploadVO;
import com.ruoyi.system.mapper.SysFileMapper;
import com.ruoyi.system.service.ISysFileService;
import io.minio.ListPartsResponse;
import io.minio.messages.Part;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 文件上传Service业务层处理
 *
 * @author ruoyi
 * @date 2024-07-01
 */
@Service
public class SysFileServiceImpl implements ISysFileService {
    @Autowired
    private SysFileMapper sysFileMapper;

    @Autowired
    private MyMinioClient myMinioClient;
    @Autowired
    private MinIoProperties minIoProperties;

    /**
     * 查询文件上传
     *
     * @param id 文件上传主键
     * @return 文件上传
     */
    @Override
    public File selectSysFileById(Long id) {
        return sysFileMapper.selectSysFileById(id);
    }

    /**
     * 查询文件上传列表
     *
     * @param sysFile 文件上传
     * @return 文件上传
     */
    @Override
    public List<File> selectSysFileList(File sysFile) {
        return sysFileMapper.selectSysFileList(sysFile);
    }

    /**
     * 新增文件上传
     *
     * @param sysFile 文件上传
     * @return 结果
     */
    @Override
    public int insertSysFile(File sysFile) {
        return sysFileMapper.insertSysFile(sysFile);
    }

    /**
     * 修改文件上传
     *
     * @param sysFile 文件上传
     * @return 结果
     */
    @Override
    public int updateSysFile(File sysFile) {
        return sysFileMapper.updateSysFile(sysFile);
    }

    /**
     * 批量删除文件上传
     *
     * @param id 需要删除的文件上传主键
     * @return 结果
     */
    @Override
    public int deleteSysFileByIds(Long id) {
        return sysFileMapper.updateSysFile(File.builder().id(id).delFlag("1").build());
    }

    /**
     * 删除文件上传信息
     *
     * @param id 文件上传主键
     * @return 结果
     */
    @Override
    public int deleteSysFileById(Long id) {
        return sysFileMapper.deleteSysFileById(id);
    }

    /**
     * 预上传
     *
     * @param params
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileUploadVO upload(FileUploadDTO params) {
        String fileMd5 = params.getFileMd5();
        String fileName = params.getFileName();
        Long fileSize = params.getFileSize();
        Long chunkSize = params.getChunkSize();
        //计算分片数量
        int chunkNum = fileSize > chunkSize ? (int) Math.ceil((double)fileSize / chunkSize) : 1;
        //获取桶
        String bucketName = getBucketName();
        //拿到存储的文件对象路径
        String objectName = DateUtil.today() + "/" + IdUtil.randomUUID() + "." + fileName.substring(fileName.lastIndexOf(".") + 1);
        List<FileUploadVO.PartVO> exitPartList = Lists.newArrayList();
        boolean isFinish = false;
        //根据文件唯一标识md5判断之前是否上传过此文件
        File file = getFileByMd5(fileMd5);
        if (ObjectUtil.isNull(file)) {
            //文件未上传 创建任务进行上传
            String uploadId = myMinioClient.getUploadId(bucketName, objectName);
            //存储数据库
            File fileDB = File.builder()
                    .uploadId(uploadId)
                    .fileMd5(fileMd5)
                    .fileName(fileName)
                    .fileExtension(FileUtil.getSuffix(fileName))
                    .bucketName(bucketName)
                    .objectName(objectName)
                    .fileSize(fileSize)
                    .chunkSize(chunkSize)
                    .chunkNum(chunkNum)
                    .createBy(SecurityUtils.getUsername())
                    .createTime(new Date())
                    .build();
            sysFileMapper.insertSysFile(fileDB);
        } else {
            if (file.isFinish()) {
                //已上传
                isFinish = true;
            } else {
                //断点续传
                boolean isExist = myMinioClient.doesObjectExist(bucketName, objectName);
                if (!isExist) {
                    // 未上传完则计算还需上传的分片
                    // 先查询已上传的分片数据
                    ListPartsResponse listPartsResponse = myMinioClient.listMultipart(bucketName, file.getObjectName(), file.getUploadId());
                    exitPartList = listPartsResponse.result().partList().stream()
                            .map(part -> FileUploadVO.PartVO.builder().partNumber(part.partNumber()).size(part.partSize()).build()).collect(Collectors.toList());
                }
            }
        }
        return FileUploadVO.builder()
                .isFinish(isFinish)
                .path(objectName)
                .taskRecord(FileUploadVO.TaskRecord.builder()
                        .fileMd5(fileMd5).chunkSize(chunkSize)
                        .chunkNum(chunkNum).exitPartList(exitPartList)
                        .build())
                .build();
    }

    /**
     * 获取每个分片的预签名上传地址
     *
     * @param fileMd5    文件唯一标识
     * @param partNumber 分片
     * @return 文件预签名上传地址
     */
    @Override
    public String getPreSignUploadUrl(String fileMd5, Integer partNumber) {
        File file = getFileByMd5(fileMd5);
        Map<String, String> reqParams = new HashMap<>();
        reqParams.put("partNumber", String.valueOf(partNumber));
        reqParams.put("uploadId", file.getUploadId());
        return myMinioClient.getPresignedObjectUrl(file.getBucketName(), file.getObjectName(), reqParams);
    }

    /**
     * 合并分片
     *
     * @param fileMd5 文件唯一标识
     */
    @Override
    public void merge(String fileMd5) {
        File file = getFileByMd5(fileMd5);
        String bucketName = file.getBucketName();
        String uploadId = file.getUploadId();
        String objectName = file.getObjectName();
        Part[] parts = new Part[10000];
        //查询分片
        ListPartsResponse partResult = myMinioClient.listMultipart(bucketName, objectName, uploadId);
        List<Part> partList = partResult.result().partList();
        int partNumber = 1;
        //封装分片
        for (Part part : partList) {
            parts[partNumber - 1] = new Part(partNumber, part.etag());
            partNumber++;
        }
        myMinioClient.completeMultipartUpload(bucketName, objectName, uploadId, parts);
        //更新文件状态
        file.setIsFinish(IsFinish.FINISH.getCode());
        sysFileMapper.updateSysFile(file);
    }

    /**
     * 下载
     *
     * @param fileMd5
     * @param response
     */
    @Override
    public void download(String fileMd5, HttpServletResponse response) {
        File file = getFileByMd5(fileMd5);
        MinIoUtil.download(file.getBucketName(), file.getObjectName(), response,file.getFileName(),file.getFileExtension());
    }


    private String getBucketName() {
        return minIoProperties.getBucketName();
    }

    private File getFileByMd5(String fileMd5) {
        return sysFileMapper.selectFileByMd5(fileMd5);
    }
}
